'''Modo Skinny is a script which takes a series of image maps (which are
tied to images) and converts them to mesh weights to be used as deformations
for character animation.

'''

# Standard
import logging
import os
# Related
# Local
import mosh.modo_session
import mosh.lib.item
import mosh.lib.mesh_element
import mosh.lib.uv
import mosh.lib.vertex_map


logger = logging.getLogger('script')

if __name__ == '__main__':
    mosh.modo_session.initialize(
        mosh_loglvl=logging.DEBUG,
        loglvl=logging.DEBUG,
    )

    # First, setup the script options.
    script_options = mosh.modo_session.ScriptOptions()
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['symmetrical_weight_maps', 'sw'],
        #help='''With this option, the output of this script will create a
        #left and right weightmap. Allowing a left and a right deformer to
        #be created per image map.
        #
        #Note that any verts at the 0.0 X position will be assigned 50% control
        #of each weight map.
        #
        #In the future, this there will be a range setting, allowing you to
        #blend the maps together.'''
    )
    script_options.add_option(
        default='letter', choices=['word', 'letter'],
        keywords=['symmetry_prefix', 'sp'],
        #help='''If set to word, symmetrical prefixes on the map names will
        #be "left_" or "right_". If set to letter, map names will be
        #prefixed with "l_" or "r_".'''
    )
    script_options.add_option(
        default=False, choices=[False, 'left', 'right'],
        keywords=['read_image_symmetrically', 'si'],
        #help='''If a vertex has a selected symmetrical partner, this
        #option gives you the ability to make Modo Skinny only read from
        #one side, or both.

        #For example, if Vertex #10 is on the right side, and Vertex #20
        #is its symmetrical partner, Vertex #10 will have it's alpha looked
        #at on the image map and Vertex #20 will have Vertex #10's same
        #value applied to it. This is useful on multiple levels.

        #First off, it means that on a symmetrical mesh, if you have
        #thousands of verts selected for Modo Skinny to analyze, it 
        #only has to read image values for half of the verts on the mesh.
        #When you think about how the image map reading is by far the
        #slowest part of MS, you can often be gaining a significant speed
        #improvement.

        #Secondly sometimes modo can slightly, at the pixel level, make
        #your image maps not fully symmetrical. It's rarely noticed, but
        #when used in such a technical way a minor non-symmetrical alpha
        #difference in an image map can result in noticeably non-symmetrical
        #character deformations.'''
    )
    script_options.add_option(
        default='largest', choices=['base', 'largest'],
        keywords=['empty_handling', 'eh'],
        #help='''This option will tell MS how you want to handle any verts
        #who end up having less than a cumulative value of 1.0.

        #"base" will create a weight map named "base_weight". This will
        #simply contain all of the weights needed to make all verts reach
        #a cumulative value of 1.0.

        #"largest" will take the same value as above, and instead of putting
        #it into a new special weight map, will instead put it into the
        #weightmap that has the most control over that vertex. This is on a
        #per vertex value of course.'''
    )
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['create_deformers', 'cd'],
        #help='''This simply creates deformers from the weight maps after
        #they have been created. Note that it will only create a deformer if
        #a deformer there is not a deformer for that map already.'''
    )
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['replace_weight_maps', 'rw'],
        #help='''This is basically only good if you need to restart a large
        #group of maps. This will simply delete each weightmap that matches
        #its target name before the new map is created.
        #
        #The alternative is to not delete them, and simply assign weights
        #based on your selection, leaving existing weights outside of your
        #selection alone. This can be an effective method to piece work a
        #character, by doing its body first, then head, then arms, etc.'''
    )

    script_options.validate_options()
    
    # Get the selected mesh id(s)
    mesh_ids = mosh.lib.item.get_selected_item_ids('mesh')
    
    if len(mesh_ids) == 0:
        raise NotImplementedError()

    elif len(mesh_ids) > 1:
        raise NotImplementedError()

    # Get the selected mesh id.
    mesh_id = mesh_ids[0]
    
    # Get the selected uv maps
    uv_map_indices = mosh.lib.vertex_map.get_selected_vmap_indices()

    # if there are more than one, raise an exception.
    if len(uv_map_indices) > 1:
        raise NotImplementedError()

    if len(uv_map_indices) == 0:
        raise NotImplementedError()

    uv_map_index = uv_map_indices[0]
    
    selected_uvs = mosh.lib.uv.get_selected_uvs(mesh_id, uv_map_index)
    
    # if are none, raise an exception.
    if len(selected_uvs) == 0:
        raise NotImplementedError()
    
    # This is what we will store posu, posv, etc.
    vertice_information = {}
    
    # Start gathering information on the verts. Starting with position.
    for polygon_index, vertex_index in selected_uvs:
        posu, posv = mosh.lib.uv.get_uv_position(mesh_id,
                                                 uv_map_index,
                                                 (polygon_index, vertex_index))
        vertex_information = {}
        vertex_information['posu'] = posu
        vertex_information['posv'] = posu
        
        vertice_information[vertex_index] = vertex_information
    
    # Write some debug info.
    logger.debug('Total UV Vertices: %05d' % len(selected_uvs))
    logger.debug('Total Vertices: %05d' % len(vertice_information))
    
    read_image_symmetrically = script_options['read_image_symmetrically']
    # Now check to see if each vertex will be read uniquely, or from
    # its symmetrical partner.
    if read_image_symmetrically:
        # Now we want to check if our symmetrical vertex has been added to
        # the vert list yet.
    
        # This is just for our debugging/stats display.
        unique_read_count = 0
        
        for vertex_index in vertice_information:
            # Here we get the X Symmetrical vertex, if any.
            symmetrical_vertex = mosh.lib.mesh_element.get_symmetrical_vertex(
                mesh_id, vertex_index)
            
            # Get the X Position of the Vertex.
            vertex_x_pos = mosh.lib.mesh_element.get_vertex_position(
                mesh_id, vertex_index)[0]
            
            if (symmetrical_vertex is not None and
                mosh.lib.mesh_element.is_vertex_selected(mesh_id,
                                                         vertex_index)):
                # If it has a sym vert and it is selected, we now check
                # which side of symmetry the user wants to read from.
                
                if (read_image_symmetrically == 'left' and
                    vertex_x_pos < 0):
                    # Because this vert is on the right side, and we want to
                    # read only from the left, we don't want to read from
                    # this vert so we give the index of the symmetrical vert.
                    vertice_information[vertex_index][
                        'read_from_symmetrical_vertex'] = (
                            vertex_index
                        )
                elif (read_image_symmetrically == 'right' and
                      vertex_x_pos > 0):
                    # Same as above, except for the left side.
                    vertex_information['read_from_symmetrical_vertex'] = (
                        vertex_index
                    )
                else:
                    # This vert cannot be read symmetrically, so it is a
                    # "unique read". As such, append the unique read count for
                    # debugging.
                    unique_read_count += 1
                    
                    vertice_information[vertex_index][
                        'read_from_symmetrical_vertex'] = None
            else:
                # This vert cannot be read symmetrically, so it is a
                # "unique read". As such, append the unique read count for
                # debugging.
                unique_read_count += 1
        
        logger.debug('Unique Reads: %05d' % unique_read_count)
    else:
        logger.debug('Unique Reads: All Reads are Unique.')
    
    
    